/*
 * Elevator.cpp
 *
 *  Created on: 22/04/2012
 *      Author: alex
 */

#include "Elevator.h"

using namespace std;

Elevator::Elevator(float averageSpeed, float maxCapacity,FloorIdentifier initialFloor,int floorAmmount,
			std::string baseFileName, int elevatorNumber, std::string keyGeneratorPath):
			m_floorProxy(floorAmmount, keyGeneratorPath,baseFileName),
			m_destinationCommunicator(floorAmmount,(char) elevatorNumber, keyGeneratorPath)
{
	m_averageSpeed = averageSpeed;
	m_maxWeight = maxCapacity;
	m_currentFloor = initialFloor;
	m_status = STOPPED;
}

Elevator::~Elevator()
{
	ElevatorPassengerList::iterator it;

	for( it=m_passengers.begin(); it!=m_passengers.end(); it++ )
		delete *it;
}

float Elevator::getCurrentWeight()
{
	float currentWeight = 0;
	ElevatorPassengerList::iterator it;


	for( it=m_passengers.begin(); it!=m_passengers.end(); it++ )
		currentWeight += (*it)->getWeight();

	return currentWeight;
}

float Elevator::getMaxCapacity()
{
	return m_maxWeight;
}

float Elevator::getCurrentCapacity()
{
	return getMaxCapacity()-getCurrentWeight();
}

void Elevator::addHallCall(FloorIdentifier destination)
{
	m_hallCalls.push_back(destination);
	m_hallCalls.unique();
	m_hallCalls.sort();
}

bool Elevator::loadPassenger(ElevatorPassenger &passenger)
{
	if( passenger.getWeight() <= getCurrentCapacity() )
	{
		m_passengers.push_back(&passenger);
		return true;
	}
	else
		return false;
}

// ToDo: Este código se puede aprolijar un poco usando splice para cada pasajero que quiero mover de una lista a la otra.
ElevatorPassengerList Elevator::unloadPassengers()
{
	ElevatorPassengerList unloadedPassengers;
	ElevatorPassengerList::iterator it;

	for( it=m_passengers.begin(); it!=m_passengers.end(); it++ )
	{
		if( (*it)->getDestination()==getCurrentFloor() )
			unloadedPassengers.push_back(*it);
	}

	for( it=unloadedPassengers.begin(); it!=unloadedPassengers.end(); it++ )
	{
		m_passengers.remove(*it);
	}

	return unloadedPassengers;
}

void Elevator::markHallCallAsServed(FloorIdentifier destination)
{
	m_hallCalls.remove(destination);
}

void Elevator::addFloorRequests(FloorIdentifierList& destinations)
{
	FloorIdentifierList::iterator it;

	for( it=destinations.begin(); it!=destinations.end(); it++ )
		addFloorRequest(*it);

}

void Elevator::addFloorRequest(FloorIdentifier destination)
{
	addHallCall(destination);
}

ElevatorStatusEnum Elevator::getStatus()
{
	return m_status;
}

FloorIdentifier Elevator::getCurrentFloor()
{
	return m_currentFloor;
}

bool Elevator::hasRequestInFloor(FloorIdentifier floor)
{
	FloorIdentifierList allCalls(getAllCalls());
	FloorIdentifierList::iterator it;

	for( it=allCalls.begin(); it!=allCalls.end(); it++ )
	{
		if( (*it)==floor )
			return true;
	}
	return false;
}

FloorIdentifierList Elevator::getCarCalls()
{
	ElevatorPassengerList::iterator it;
	FloorIdentifierList passengerDestinations;

	for ( it=m_passengers.begin(); it!=m_passengers.end(); it++ )
		passengerDestinations.push_back( (*it)->getDestination() );

	passengerDestinations.unique();
	passengerDestinations.sort();

	return passengerDestinations;
}

FloorIdentifierList& Elevator::getHallCalls()
{
	return m_hallCalls;
}

FloorIdentifierList Elevator::getAllCalls()
{
	FloorIdentifierList allCalls;
	FloorIdentifierList::iterator it;

	// Luego agrego las llamadas de cabina a la lista completa de llamadas.
	allCalls = getCarCalls();

	// Luego agrego las llamadas de palier a la lista de llamadas completas.
	for ( it=getHallCalls().begin(); it!=getHallCalls().end(); it++ )
		allCalls.push_back( *it );

	// Finalmente orderno y elimino duplicados de la lista completa de llamadas.
	allCalls.unique();
	allCalls.sort();

	return allCalls;
}

bool Elevator::hasPassengersToUnloadAt(FloorIdentifier floor)
{
	ElevatorPassengerList::iterator it;
	bool hasPassengersToUnloadAtFloor=false;

	for( it=m_passengers.begin(); it!=m_passengers.end(); it++ )
	{
		if( (*it)->getDestination()==getCurrentFloor() )
		{
			hasPassengersToUnloadAtFloor=true;
		}
	}

	return hasPassengersToUnloadAtFloor;
}

bool Elevator::willStopAtCurrentFloor()
{
	//return hasRequestInFloor(m_currentFloor);
	return (getCurrentCapacity() > 0 and hasRequestInFloor(m_currentFloor)) or hasPassengersToUnloadAt(m_currentFloor);
}

ElevatorStatusEnum Elevator::getNextStatus()
{
	ElevatorStatusEnum nextStatus;

	if ( getCurrentCapacity() > 0 )
	{
		// El ascensor todavia puede levantar pasajeros

		// Decide que hacer en base a la la Lista de Pedidos Completa (lista de llamadas completa)
		nextStatus = getNextStatusFrom( getAllCalls() );
	}
	else
	{
		// El ascensor esta lleno

		// Decide que hacer en base a la la Lista de Pedidos De Cabina
		nextStatus = getNextStatusFrom( getCarCalls() );
	}

	return nextStatus;
}

ElevatorStatusEnum Elevator::getNextStatusFrom( FloorIdentifierList destinations )
{
	ElevatorStatusEnum nextStatus;

	if ( destinations.empty() )
		nextStatus = STOPPED;
	else
		if ( destinations.size() == 1 and destinations.front() == m_currentFloor )
			nextStatus = STOPPED;
		else
			switch ( m_status )
			{
				case STOPPED:
					if ( destinations.back() > m_currentFloor )
						nextStatus = GOING_UP;
					else
						nextStatus = GOING_DOWN;
					break;

				case GOING_UP:
					if ( destinations.back() > m_currentFloor )
						nextStatus = GOING_UP;
					else
						nextStatus = GOING_DOWN;
					break;

				case GOING_DOWN:
					if ( destinations.front() < m_currentFloor )
						nextStatus = GOING_DOWN;
					else
						nextStatus = GOING_UP;
					break;
			}
	return nextStatus;
}

void Elevator::handleRequests()
{
	ElevatorStatusEnum nextStatus;

	markHallCallAsServed(m_currentFloor);
	nextStatus = getNextStatus();

	if( nextStatus!=STOPPED )
		sleep(FLOOR_DISTANCE/m_averageSpeed);

	m_status = nextStatus;
	switch(m_status)
	{
		case GOING_UP:
			m_currentFloor++;
			break;

		case GOING_DOWN:
			m_currentFloor--;
			break;

		case STOPPED:
			break;
	}

}

int Elevator::getPassengerCount()
{
	return m_passengers.size();
}

void Elevator::startSimulation()
{
    m_floorProxy.start(false);

    m_doRun = true;
    m_keepAceptingPassengers = true;

    FloorIdentifier currentFloor;
	FloorIdentifierList callingFloors;
	FloorIdentifierList::iterator floorIt;
	ElevatorPassengerList unloadedPassengers;
	ElevatorPassenger *passenger;
	ElevatorStatus elevatorStatus;
	bool aPassengerWasRejected=false;

	CLogger::log("The elevator starts at floor: "+StringUtils::NumberToString(getCurrentFloor())+".",LogLevelEnum::DEBUG);

	//Inicializo el ascensor
	elevatorStatus.hasNewCalls = false;
	elevatorStatus.m_currentDirection =STOPPED;
	elevatorStatus.m_currentFloor = getCurrentFloor();

	m_destinationCommunicator.setElevatorStatus(elevatorStatus);

    while(m_doRun)
    {
    	currentFloor = getCurrentFloor();

    	elevatorStatus = m_destinationCommunicator.getElevatorStatus();

    	if(elevatorStatus.hasNewCalls)
    	{
			callingFloors = m_destinationCommunicator.getHallCalls();
			addFloorRequests(callingFloors);

//	    	CLogger::log("Elevator received new requests",LogLevelEnum::DEBUG);

//			printIdentifierList(callingFloors);

	    	CLogger::log("New request received "+identifierListToString(callingFloors),
	    				LogLevelEnum::DEBUG);
    	}

//    	//TODO remove
//    	sleep(1);

    	//Si el Elevador tiene marcado el piso en el que se encuentra actualmente como destino
    	if(willStopAtCurrentFloor())
    	{
    		CLogger::log("The elevator stops on floor: "+StringUtils::NumberToString(currentFloor)+".",LogLevelEnum::DEBUG);
    		CLogger::log("The elevator has: "+StringUtils::NumberToString(getPassengerCount())+" passengers before unloading.",LogLevelEnum::DEBUG);

    		//Descargo los pasajeros del ascensor
    		unloadedPassengers = unloadPassengers();

    		CLogger::log("The elevator has: "+StringUtils::NumberToString(getPassengerCount())+" passengers after unloading.",LogLevelEnum::DEBUG);
    		//Destruyo los pasajeros descargados
    		if( !unloadedPassengers.empty() )
    		{
    			ElevatorPassengerList::iterator it;
    			CLogger::log("The elevator unloads "+StringUtils::NumberToString(unloadedPassengers.size())+" passengers.",LogLevelEnum::DEBUG);

    			for(it = unloadedPassengers.begin(); it !=unloadedPassengers.end();it++)
    				delete *it;

    			unloadedPassengers.clear();
    		}

    		if( m_keepAceptingPassengers )
    		{
				CLogger::log("The elevator has: "+
						StringUtils::NumberToString(getPassengerCount())+" passengers before loading.",LogLevelEnum::DEBUG);

				//Si hay pasajeros en el piso
				while(m_floorProxy.hasPassengers(currentFloor))
				{
					//Me fijo el primer registro de la queue sin sacarlo del piso
					passenger = m_floorProxy.peekPassenger(currentFloor);
					CLogger::log("A passenger tries to enter the elevator.",LogLevelEnum::DEBUG);

					// Intento subir el pasajero al Elevador, si tengo exito saco de la cola de espera.
					if(loadPassenger(*passenger))
					{
						m_floorProxy.deletePeekedPassenger(currentFloor);
						CLogger::log("The passenger with destination: "+StringUtils::NumberToString(passenger->getDestination())+" successfully enters the elevator.",LogLevelEnum::DEBUG);
					}
					else
					{
						CLogger::log("The passenger is unable to enter the elevator, it's full.",
								LogLevelEnum::DEBUG);
						aPassengerWasRejected=true;
						break;
					}
				}
				CLogger::log("The elevator has: "+StringUtils::NumberToString(getPassengerCount())+" passengers after loading.",LogLevelEnum::DEBUG);
			}
    		else
    		{
    			CLogger::log("The elevator does not accept passengers anymore.",
    					LogLevelEnum::DEBUG);

    		}
    	}

    	// Avanzo un piso
		handleRequests();

		// Si me habia quedado un pasajero que no pudo subir al elevador
		// porque este estaba lleno hago que vuelva a llamar al ascensor.
		// Esto lo hago acá porque en handleRequests() el ascensor marco
		// el piso como atendido.
		if( aPassengerWasRejected )
		{
			addFloorRequest(currentFloor);
			aPassengerWasRejected = false;
			CLogger::log("The rejected passenger calls the elevator again.",
					LogLevelEnum::DEBUG);
		}

		if( getStatus() != STOPPED )
			CLogger::log("The elevator arrives at floor: "+
					StringUtils::NumberToString(getCurrentFloor())
					+".",LogLevelEnum::DEBUG);


		if(elevatorStatus.hasNewCalls)
		{
			elevatorStatus.m_currentDirection = getStatus();
			elevatorStatus.m_currentFloor = m_currentFloor;
			elevatorStatus.hasNewCalls = false;
			m_destinationCommunicator.setElevatorStatus(elevatorStatus);
		}
		else
			m_destinationCommunicator.setMovementStatus(m_currentFloor,getStatus());


		m_destinationCommunicator.setAllCalls(getAllCalls());

		//Si deje de correr y agote los pasajeros del ascensor
		//termino el ciclo principal
		if( !m_keepAceptingPassengers and getPassengerCount() ==0 )
		{
			CLogger::log("No more passengers",LogLevelEnum::DEBUG);
			m_doRun = false;
		}
    }
}

void Elevator::stopSimulation()
{
	m_keepAceptingPassengers = false;
}
